/**
 * NESDroid
 * Copyright 2011 Stephen Damm (Halsafar)
 * All rights reserved.
 * shinhalsafar@gmail.com
 */
#include "Application.h"

#include "fceu_src/fceu.h"
#include "fceu_src/ppu.h"
#include "fceu_src/driver.h"

#include "libzip/zip.h"


Application::Application():
     Input(BUTTON_INDEX_COUNT, 1)
{
     _initialized = false;
     _romLoaded = false;
     _fceuInitialized = false;
     _audioInitialized = false;
     _gfx = NULL;
     _sound = NULL;
     _ssize = 0;
     _outSound = NULL;

     // paths
     _apkPath = NULL;
}


Application::~Application()
{
     LOGD("Application::Destructor()");
     destroy();
}



void Application::destroy()
{
     LOGD("Start Application.destroy()");

     // don't delete these in destroy
     if (_outSound != NULL)
     {
          delete _outSound;
          _outSound = NULL;
     }

     if (_apkPath != NULL)
     {
          free(_apkPath);
          _apkPath = NULL;
     }

     // TODO: take auto save snapshot

     if (_fceuInitialized)
     {
          // close - causes sram save
          FCEUI_CloseGame();

          // kill FCEU
          FCEUI_Kill();

          _fceuInitialized = false;
     }

     _romLoaded = false;

     LOGD("Finished Application.destroy()");
}


int Application::init(JNIEnv *env, const char* apkAbsolutePath, const char* externalStorageDir)
{
     //assert(apkAbsolutePath != NULL && externalStorageDir != NULL);

     LOGD("APK_PATH: %s", apkAbsolutePath);
     LOGD("EXTERNAL_STORAGE_DIR: %s", externalStorageDir);

     // init fceu default
     FCEUI_SetBaseDirectory(externalStorageDir);

     if (apkAbsolutePath == NULL || strlen(apkAbsolutePath) >= MAX_PATH)
     {
          return NATIVE_ERROR;
     }

     // copy dirs for storage
     if (_apkPath == NULL)
     {
          _apkPath = (char*)malloc(MAX_PATH * sizeof(char));
          memset(_apkPath, 0, MAX_PATH * sizeof(char));
     }
     strcpy(_apkPath, apkAbsolutePath);

     if (!_fceuInitialized)
     {
          FCEUI_Initialize();

          _fceuInitialized = true;
     }

     _initialized = true;

     LOGD("DONE INIT");

     return NATIVE_OK;
}


int Application::initAudioBuffers(const int sizeInSamples)
{
     LOGD("initAudioBuffers(%d)", sizeInSamples);
     if (_outSound != NULL)
     {
          delete _outSound;
          _outSound = NULL;
     }

     _bufferSize = sizeInSamples;
     _outSound = new short[sizeInSamples];

     _audioInitialized = true;

     return NATIVE_OK;
}


int Application::initGraphics()
{
     LOGD("initGraphics()");
     Graphics.Init();

     Graphics.InitEmuShader(NULL, NULL);
     Graphics.Clear();

     LOGI("Loading APK %s", _apkPath);
     zip* APKArchive = zip_open(_apkPath, 0, NULL);
     if (APKArchive == NULL)
     {
          LOGE("Error loading APK");
          return NATIVE_ERROR;
     }

     int dPadTex = 0;
     if (Graphics.loadPNGTexture(APKArchive, "assets/Textures/DirectionalPad.png", &dPadTex) != GRAPHICS_OK)
     {
          return NATIVE_ERROR;
     }

     int startTex = 0;
     if (Graphics.loadPNGTexture(APKArchive, "assets/Textures/StartButton.png", &startTex) != GRAPHICS_OK)
     {
          return NATIVE_ERROR;
     }

     int selectTex = 0;
     if (Graphics.loadPNGTexture(APKArchive, "assets/Textures/SelectButton.png", &selectTex) != GRAPHICS_OK)
     {
          return NATIVE_ERROR;
     }

     int bTex = 0;
     if (Graphics.loadPNGTexture(APKArchive, "assets/Textures/ButtonB.png", &bTex) != GRAPHICS_OK)
     {
          return NATIVE_ERROR;
     }

     int aTex = 0;
     if (Graphics.loadPNGTexture(APKArchive, "assets/Textures/ButtonA.png", &aTex) != GRAPHICS_OK)
     {
          return NATIVE_ERROR;
     }

     int rewindTex = 0;
     if (Graphics.loadPNGTexture(APKArchive, "assets/Textures/Rewind.png", &rewindTex) != GRAPHICS_OK)
     {
          return NATIVE_ERROR;
     }

     int fastForwardTex = 0;
     if (Graphics.loadPNGTexture(APKArchive, "assets/Textures/FastForward.png", &fastForwardTex) != GRAPHICS_OK)
     {
          return NATIVE_ERROR;
     }

     zip_close(APKArchive);

     Input.setAnalogTexture(0, dPadTex);
     Input.setButtonTexture(BUTTON_INDEX_B, bTex);
     Input.setButtonTexture(BUTTON_INDEX_A, aTex);
     Input.setButtonTexture(BUTTON_INDEX_START, startTex);
     Input.setButtonTexture(BUTTON_INDEX_SELECT, selectTex);
     Input.setButtonTexture(BUTTON_INDEX_REWIND, rewindTex);
     Input.setButtonTexture(BUTTON_INDEX_FORWARD, fastForwardTex);

     return NATIVE_OK;
}


void Application::setAudioEnabled(const bool b)
{
     if (b)
     {
          //FCEUI_Sound(FCEU_AUDIO_SAMPLE_RATE);
          FCEUI_SetSoundVolume(100);
          FCEUI_SetLowPass(0);
          FCEUI_SetSoundQuality(0);
          _audioInitialized = true;
     }
     else
     {
          //FCEUI_Sound(0);
          _audioInitialized = false;
     }
}


void Application::saveState(const int i)
{
     FCEUI_SelectState(i, true);
     FCEUI_SaveState(NULL);
}


void Application::loadState(const int i)
{
     FCEUI_SelectState(i, true);
     FCEUI_LoadState(NULL);
}


void Application::selectState(const int i)
{
     FCEUI_SelectState(i, true);
}


int Application::loadROM(const char* filename)
{
     LOGD("NDK:LoadingRom: %s", filename);

     if (!_fceuInitialized)
     {
          FCEUI_Initialize();
          _fceuInitialized = true;
     }

     if (_romLoaded)
     {
          FCEUI_CloseGame();
          _romLoaded = false;
     }

     //ResetGameLoaded();
     FCEUGI* gi = FCEUI_LoadGame(filename, 1);
     if (gi == NULL)
     {
          return NATIVE_ERROR;
     }

     _InputDPR = &_JSReturn;

    // Default ports back to gamepad
    FCEUI_SetInput(0, SI_GAMEPAD, _InputDPR, 0);

    //FCEUI_DisableSpriteLimitation(1);
    //FCEU_TogglePPU();

    //FCEUI_Autosave();

    _romLoaded = true;

     return NATIVE_OK;
}


// consumer, java driven
size_t Application::readAudio(jshort* in, const size_t samples)
{
     if (_ssize >= 0 && _ssize <= 1024 && _outSound != NULL)
     {
          memcpy(in, _outSound, _ssize * sizeof(short));
     }

     return _ssize;
}


void Application::processInput()
{
     _JSReturn = 0;
     if (Input.isButtonDown(BUTTON_INDEX_DOWN) ||
         Input.getAnalogY(0) > Input.getYSensitivity())
     {
          _JSReturn |= JOY_DOWN;
     }

     if (Input.isButtonDown(BUTTON_INDEX_RIGHT) ||
         Input.getAnalogX(0) > Input.getXSensitivity())
     {
          _JSReturn |= JOY_RIGHT;
     }

     if (Input.isButtonDown(BUTTON_INDEX_LEFT) ||
         Input.getAnalogX(0) < -Input.getXSensitivity())
     {
          _JSReturn |= JOY_LEFT;
     }

     if (Input.isButtonDown(BUTTON_INDEX_UP) ||
         Input.getAnalogY(0) < -Input.getYSensitivity())
     {
          _JSReturn |= JOY_UP;
     }

     if (Input.isButtonDown(BUTTON_INDEX_B))
     {
          _JSReturn |= JOY_B;
     }

     if (Input.isButtonDown(BUTTON_INDEX_A))
     {
          _JSReturn |= JOY_A;
     }

     if (Input.isButtonDown(BUTTON_INDEX_START))
     {
          _JSReturn |= JOY_START;
     }

     if (Input.isButtonDown(BUTTON_INDEX_SELECT))
     {
          _JSReturn |= JOY_SELECT;
     }

     if (Input.isButtonDown(BUTTON_INDEX_FORWARD))
     {
          //_frameSkipCount=(_frameSkipCount+1)%(18+1);
          for (int i = 0; i < TURBO_FRAMES_TO_EMULATE; i++)
          {
               FCEUI_Emulate(&_gfx, &_sound, &_ssize, 1);
          }
     }

     if (Input.isButtonDown(BUTTON_INDEX_REWIND))
     {
          FCEUI_Autosave();
          usleep(60000);
     }

     Input.update();
}


void Application::step(const JNIEnv *env)
{
     //LOGD("STEP");

     // quick exit if no rom loaded to step
     if (!_romLoaded)
     {
          return;
     }

     // calculate frameskipping
     _frameSkipCount=(_frameSkipCount+1)%(_frameSkip+1);

     //LOGD("PROCESS INPUT");
     processInput();

     // calc framerate
     /*_timeStart = _timeEnd;
     _timeEnd = now_ms();
     _timeDelta = (_timeEnd - _timeStart);
     FCEUI_printf("FPS: %g", _timeDelta);
     */

    _sound = NULL;
    _gfx = NULL;
    _ssize = 0;
    FCEUI_Emulate(&_gfx, &_sound, &_ssize, _frameSkipCount);

    // render the frame
    if(_gfx)
    {
         Graphics.DrawNES(_gfx, 256, 240);
    }


    if(_sound && _ssize > 0)
    {
        //LOGD("SOUND SIZE: %d", _ssize);

        for (size_t i = 0; i < _ssize; i++)
        {
             _outSound[i] = _sound[i];
        }
    }

    //LOGD("DONE STEP");
}


void Application::draw(const JNIEnv* env)
{
    //LOGD("Clearing");
    Graphics.Clear();

    //LOGD("Drawing");
    Graphics.Draw();

    // draw buttons
    //LOGD("Draw Buttons");
    Input.draw(Graphics);
}
